import * as TRACK_TYPES from '../tracks/track-types';
import Component from '../component'
import mergeOptions from '../utils/merge-options'
import * as Fn from '../utils/fn'
import log from '../utils/log'
import { createTimeRange } from '../utils/time-ranges'
import { bufferedPercent } from '../utils/buffer'
import MediaError from '../media-error'
import window from 'global/window'
import document from 'global/document'
import { isPlain } from '../utils/obj'
import toTitleCase from '../utils/to-title-case'
import vtt from 'videojs-vtt'

/**
 * 给Tech用来穿件新的TextTrack的方法
 * @param {Tech} self
 * @param {string} kind TextTrack的类型(subtitles, captions, descriptions, chapters, or metadata)
 * @param {*} label 用来识别text track的label
 * @param {*} language 语言类型 zh,en
 * @param {*} options 附加的text track options
 * @return {TextTrach}
 */
function createTrackHelper (self, kind, label, language, options) {
  const tracks = self.textTracks()

  options.kind = kind
  if (label) {
    options.label = label
  }
  if (language) {
    options.language = language
  }
  options.tech = self
  const track = new TRACK_TYPES.ALL.text.TrackClass(options)
  tracks.addTrack(track)
  return track
}

class Tech extends Component {

  /**
   *
   * @param {object} options
   * @param {Component~ReadyCallback} ready
   */
  constructor (options = {}, ready = function () {}) {

    // 不让tech报告用户行为，这已经在addControlsListeners中自动处理了
    options.reportTouchActivity = false
    super(null, options, ready)

    // 追踪是否播放过了
    this.hasStarted_ = false
    this.on('playing', function () {
      this.hasStarted_ = true
    })
    this.on('loadstart', function () {
      this.hasStarted_ = false
    })

    TRACK_TYPES.ALL.names.forEach(name => {
      const props = TRACK_TYPES.ALL[name]
      if (options && options[props.getterName]) {
        this[props.privateName] = options[props.getterName]
      }
    })

    // 手动追踪video和flash的进度
    if (!this.featuresProgressEvents) {
      this.manualProgressOn()
    }

    // 手动追踪browser/flash的时间进度更新
    if (!this.featuresTimeupdateEvents) {
      this.manualTimeUpdatesOn()
    }

    ['Text', 'Audio', 'Video'].forEach(track => {
      if (options[`native${track}Tracks`] === false) {
        this[`featuresNavive${track}Tracks`] = false
      }
    })

    if (options.nativeCaptions === false || options.nativeTextTracks === false) {
      this.featuresProgressEvents = false
    } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
      this.featuresNativeTextTracks = true
    }

    // 模拟字幕
    if (!this.featuresNativeTextTracks) {
      this.emulateTextTracks()
    }

    this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass()
    this.initTrackListeners()

    // 如果没有使用原生的控制条的话，就开启component的tap事件监听
    if (!options.nativeControlsForTouch) {
      this.emitTapEvents()
    }
    if (this.constructor) {
      this.name_ = this.constructor.name || 'Unknown Tech'
    }
  }

  /**
   * 触发source的设定, 如果player没有ready的话这将允许player去重复触发
   * @param {*} src
   */
  triggerSourceset (src) {
    if (!this.isReady_) {
      this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1))
    }

    /**
     * @see Player#event:sourceset
     */
    this.trigger({
      src,
      type: 'sourceset'
    })
  }

  /**
   * 开启VdeoTrackList, AudioTrackList, TextTrackList 的事件监听
   *
   * 这个函数添加了addtrack, removetrack的事件监听
   *
   */
  initTrackListeners () {
    TRACK_TYPES.NORMAL.names.forEach(name => {
      const props = TRACK_TYPES.NORMAL[name]
      const trackListChanges = () => {
        this.trigger(`${name}trackchange`)
      }
      const tracks = this[props.getterName]()

      tracks.addEventListener('removetrack', trackListChanges)
      tracks.addEventListener('addtrack', trackListChanges)

      this.on('dispose', () => {
        tracks.removeEventListener('removetrack', trackListChanges)
        tracks.removeEventListener('addtrack', trackListChanges)
      })
    })
  }

  /**
   * 关闭所有事件的polyfill, 清除Tech的{@link AudioTrackList},
   * {@link VideoTRackList}, and {@link TextTrackList}, and dispose of this Tech.
   *
   * @fires Component#dispose
   */
  dispose () {
    this.clearTracks(TRACK_TYPES.NORMAL.names)
    if (this.manualProgress) {
      this.manualProgressOff()
    }
    if (this.manualTimeUpdates) {
      this.manualTimeUpdatesOff()
    }
    super.dispose()
  }

  /**
   * 根据name清除单个TrackList或者一个TrackLists数组
   * @param {*} types
   */
  clearTracks (types) {
    types = [].concat(types)
    types.forEach(type => {
      const list = this[`${type}Tracks`]() || []
      let i = list.length;
      while(i--) {
        const track = list[i]
        if (type === 'text') {
          this.removeRemoteTextTrack(track)
        }
        list.removeTrack(track)
      }
    })
  }

  /**
   * 从TextTrackList中删除一个remote text track
   * @param {TextTrack} track
   */
  removeRemoteTextTrack (track) {
    const trackElement = this.removeTextTrackEls().getTrackElementByTrack_(track)

    // 从remote list中删除HTMLTrackElement和TextTrack
    this.remoteTextTrackEls().removeTrackElement_(trackElement)
    this.remoteTextTracks().removeTrack(track)
    this.autoRemoteTextTracks_.removeTrack(track)

  }

  /**
   * 模拟texttracks ?? textTracks/remoteTracks方法在哪里
   * wtf ?? tracks和remoteTracks有什么区别, on/addEventListener
   */
  emulateTextTracks () {
    const tracks = this.textTracks()
    const remoteTracks = this.remoteTracks()
    const handleAddTrack = e => tracks.addTrack(e.track)
    const handleRemoveTrack = e => tracks.removeTrack(e.track)

    remoteTracks.on('addtrack', handleAddTrack)
    remoteTracks.on('removetrack', handleRemoveTrack)

    this.addWebVttScript_()

    const updateDisplay = () => this.trigger('texttrackchange')
    const textTracksChanges = () => {
      updateDisplay()

      for (let i = 0; i < tracks.length; i++) {
        const track = tracks[i]

        track.removeEventListener('cuechange', updateDisplay)
        if (track.mode === 'showing') {
          track.addEventListener('cuechange', updateDisplay)
        }
      }
    }

    textTracksChanges()
    tracks.addEventListener('change', textTracksChanges)
    tracks.addEventListener('addtrack', textTracksChanges)
    tracks.addEventListener('removeTrack', textTracksChanges)

    this.on('dispose', function () {
      remoteTracks.off('addtrack', handleAddTrack)
      remoteTracks.off('removetrack', handleRemoveTrack)
      tracks.removeEventListener('change', textTracksChanges)
      tracks.removeEventListener('addtrack', textTracksChanges)
      tracks.removeEventListener('removetrack', textTracksChanges)

      for (let i = 0; i < tracks.length; i++) {
        const track = tracks[i]
        track.removeEventListener('cuechange', updateDisplay)
      }
    })
  }

  /**
   * 如果必要的话使用vtt.js来模拟texttracks
   */
  addWebVttScript_ () {
    if (window.WebVTT) {
      return
    }

    //
    if (document.body.contains(this.el())) {
      if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
        this.trigger('vttjsloaded')
        return
      }

      const script = document.createElement('script')
      script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'
      script.onload = () => {
        this.trigger('vttjsloaded')
      }
      script.onerror = () => {
        this.trigger('vttjserror')
      }
      this.on('dispose', () => {
        script.onload = null
        script.onerror = null
      })
      window.WebVTT = true
      this.el().parentNode.appendChild(script)
    } else {
      this.ready(this.addWebVttScript_)
    }
  }

  /**
   * 对于浏览器原生不支持的progress事件进行polyfill
   * @see {@link Tech@trackProgress}
   */
  manualProgressOn () {
    this.on('durationchange', this.onDurationChange)
    this.manualProgress = true

    // 当开始loading时，手动触发progress监听
    this.one('ready', this.trackProgress)
  }

  /**
   * 对于浏览器原生不支持的timeupdate事件进行polyfill
   */
  manualTimeUpdatesOn () {
    this.manualTimeUpdates = true
    this.on('play', this.trackCurrentTime)
    this.on('pause', this.stopTrackingCurrentTime)
  }

  /**
   * 关闭timeupdate事件的polyfill
   * {@link Tech#manualTimeUpdatesOn}
   */
  manualTimeUpdatesOff () {
    this.manualTimeUpdates = false
    this.stopTrackingCurrentTime()
    this.off('play', this.trackCurrentTime)
    this.off('pause', this.stopTrackingCurrentTime)
  }

  /**
   * 使用setinterval每250ms来追踪timeupdate
   */
  trackCurrentTime () {
    if (this.currentTimeInterval) {
      this.stopTrackingCurrentTime()
    }
    this.currentTimeInterval = this.setInterval(function () {
      this.trigger({type: 'timeupdate', target: this, manuallyTriggered: true})

    // 250是webkit的使用, firefox使用15
    }, 250)
  }

  stopTrackingCurrentTime () {
    this.clearInterval(this.currentTimeInterval)

    // 如果视频先播放结束，这时候setinterval没走到，timeupdate就不会触发
    this.trigger({type: 'timeupdate', target: this, manuallyTriggered: true})
  }

  /**
   * 在durationchange事件触发时，更新内部的duration变量
   * @param {*} event
   */
  onDurationChange (event) {
    this.duration_ = this.duration()
  }

  /**
   * 这个方法用来当缓冲段改变的时候触发progress事件, 每500ms检查一次buffer的改变
   * @param {*} event
   */
  trackProgress (event) {
    this.stopTrackingProgress()
    this.progressInterval = this.setInterval(Fn.bind(this, function () {
      const numBufferedPercent = this.bufferedPercent()

      if (this.bufferedPercent_ !== numBufferedPercent) {
        this.trigger('progress')
      }

      this.bufferedPercent_ = numBufferedPercent

      if (this.bufferedPercent === 1) {
        this.stopTrackingProgress()
      }
    }))
  }

  /**
   * 获取并创建一个TimeRange对象给buffering
   * @return {TimeRange}
   */
  buffered () {
    return createTimeRange(0, 0)
  }

  /**
   * 获取当前视频的buffer百分比
   * @return {number} 0~1
   */
  bufferedPercent () {
    return bufferedPercent(this.buffered(), this.duration_)
  }

  /**
   * 停止buffer的track
   */
  stopTrackingProgress () {
    this.clearInterval(this.progressInterval)
  }

  /**
   * 清除通过addRemoteTextTrack添加的TextTracks
   */
  cleanupAutoTextTracks () {
    const list = this.autoRemoteTextTracks_ || []
    let i = list.length
    while (i--) {
      const track = list[i]
      this.removeRemoteTextTrack(track)
    }
  }

  /**
   * 重置Tech, 将会移除所有sources并且重置内部的ready状态
   */
  reset() {}

  /**
   * 获取或者设置Tech的错误
   * @param {*} err
   */
  error (err) {
    if (err !== undefined) {
      this.error_ = new MediaError(err)
      this.trigger('errro')
    }
    return this.error_
  }

  /**
   * 返回当前已经播放了的TimeRange
   */
  played () {
    if (this.hasStarted_) {
      return createTimeRange(0, 0)
    }
    return createTimeRange()
  }

  /**
   * 如果{@link Tech#manualTimeUpdatesOn}先被调用了, 就手动更新下time
   * @fires Tech#timeupdate
   */
  setCurrentTime () {
    if (this.manualTimeUpdates) {
      this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true})
    }
  }


  /**
   * 创建并返回一个 remote {@link TextTrack} 对象
   */
  addTextTrack (kind, label, language) {
    if (!kind) {
      throw new Error('TextTrack kind is required but was not provided')
    }
    return createTrackHelper(this, kind, label, language)
  }

  /**
   * 创建一个模拟的TextTrack，给addRemoteTextTrack使用
   * @param {} options
   */
  createRemoteTextTrack (options) {
    const track = mergeOptions(options, {
      tech: this
    })
    return TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track)
  }

  /**
   * 创建一个remote text track 对象并返回一个html track 元素
   * @param {i} options
   * @param {*} manualCleanup
   */
  addRemoteTextTrack (options = {}, manualCleanup) {
    const htmlTrackElement = this.createRemoteTextTrack(options)

    if (manualCleanup !== true && manualCleanup !== false) {
      log.warn('manucleanup在后面会默认成false')
      manualCleanup = true
    }

    // 存储TextTrack和HTMLTrackElement到remote lsit
    this.remoteTextEls().addTrackElement_(htmlTrackElement)
    this.remoteTracks().addTrack(htmlTrackElement.track)

    if (manualCleanup !== true) {
      // 如果TExtTrackList不存在就创建
      this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track))
    }

    return htmlTrackElement
  }

  /**
   * Gets available media playback quality metrics as specified by the W3C's Media
   * Playback Quality API.
   *
   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
   *
   * @return {Object}
   *         An object with supported media playback quality metrics
   *
   * @abstract
   */
  getVideoPlaybackQuality () {
    return {}
  }

  /**
   * 设置封面
   */
  setPoster() {}

  /**
   * 检查video元素上playsinline的属性
   */
  playsinline () {}

  setPlaysinline() {}

  /**
   * 强制重新原声的audio tracks
   * @abstract
   */
  overrideNativeAudioTracks() {}
  overrideNativeVideoTracks() {}

  /**
   * @abstract
   */
  canPlayType() {
    return '';
  }

  static canPlayType() {
    return '';
  }

  static canPlaySource(srcObj, options) {
    return Tech.canPlayType(srcObj.type);
  }

  static isTech(component) {
    return component.prototype instanceof Tech ||
           component instanceof Tech ||
           component === Tech;
  }

  static registerTech (name, tech) {
    if (!Tech.techs_) {
      Tech.techs_ = {}
    }
    if (!Tech.isTech(tech)) {
      throw new Error(`Tech ${name} must be a tech`)
    }
    if (!Tech.canPlayType) {
      throw new Error('必须要有static canPlayType')
    }
    if (!Tech.canPlaySource) {
      throw new Error('必须要有static canPlaySource')
    }

    name = toTitleCase(name)

    Tech.techs_[name] = tech
    if (name !== 'Tech') {
      Tech.defaultTechOrder_.push(name)
    }
    return tech
  }

  static getTech (name) {
    if (!name) {
      return
    }
    name = toTitleCase(name)

    if (Tech.techs_ && Tech.techs_[name]) {
      return Tech.techs_[name]
    }

    if (window && window.videojs && window.videojs[name]) {
      log.warn(`${name} 当使用videojs.registerTech注册的时候，tech被添加到videojs object`)
      return window.videojs[name]
    }
  }
}


/**
 * 创建remote element {@link HtmlTrackElementList}
 * @returns {HtmlTrackElementList}
 * @method Tech.prototype.remoteTextTrackEls
 */
TRACK_TYPES.ALL.names.forEach(function (name) {
  const props = TRACK_TYPES.ALL[name]

  Teck.prototype[props.getterName] = function () {
    this[props.privateName] = this[props.privateName] || new props.ListClass()
    return this[props.privateName]
  }
})

/**
 * Tech是否支持音量控制
 * @type {boolean}
 */
Tech.prototype.featuresVolumeControl = true

/**
 * Tech是否支持全屏时的resize控制
 */
Tech.prototype.featuresFullscreenResize = false

/**
 * Tech是否支持改变视频播放速度
 */
Tech.prototype.featuresPlaybackRate = false

/**
 * Tech是否支持progress事件。暂时没有被video-js-swf触发。这个值会被用来决定
 * {@link Tech#manualProgressOn}是否应该被调用
 */
Tech.prototype.featureProgressEvents = false

/**
 * Tech是否支持timeupdate事件.暂时没有被video-js-swf触发。这个值会被用来决定
 * {@link Tech# manualTimeUpdates}是否应该被调用
 */
Tech.prototype.featuresTimeupdateEvents = false

Tech.prototype.featuresNativeTextTracks = false

/**
 * 一个给techs用的函数式的mixin.
 * @param {*} _Tech
 */
Tech.withSourceHandlers = function (_Tech) {

  /**
   * 注册源处理器
   * @param {*} handler
   * @param {*} index
   */
  _Tech.registerSourceHandler = function (handler, index) {
    let handlers = _Tech.sourceHandlers

    if (!handlers) {
      handlers = _Tech.sourceHandlers = []
    }
    if (index === undefined) {
      index = handlers.length
    }
    handlers.splice(index, 0, handler)
  }

  /**
   * 使用handlers检查是否能播放
   * @param {*} type
   */
  _Tech.canPlayType = function (type) {
    const handlers = _Tech.sourceHandlers || []
    let can

    for (let i = 0; i < handlers.length; i++) {
      can = handlers[i].canPlayType(type)
      if (can) {
        return can
      }
    }
    return ''
  }

  /**
   * 使用第一个支持当前source的处理器
   * @param {*} source
   * @param {*} options
   */
  _Tech.selectSourceHandler = function (source, options) {
    const handlers = _Tech.canHandleSource(source, options)

    let can
    for (let i = 0; i < handlers.length; i++) {
      can = handlers[i].canHandleSource(source, options)
      if (can) {
        return handlers[i]
      }
    }
    return null
  }

  /**
   * 是否能播放源
   * @param {*} srcObj
   * @param {*} options
   */
  _Tech.canPlaySource = function (srcObj, options) {
    const sh = _Tech.selectSourceHandler(srcObj, options)
    if (sh) {
      return sh.canHandleSource(srcObj, options)
    }
    return ''
  }

  /**
   *
   */
  const deferrable = [
    'seekable',
    'seeking',
    'duration'
  ]

  deferrable.forEach(function (fnName) {
    const originalFn = this[fnName]
    if (typeof originalFn !== 'function') {
      return
    }
    this[fnName] = function () {
      if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
        return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments)
      }
      return originalFn.apply(this, arguments)
    }
  }, _Tech.prototype)

  /**
   * 创建一个函数用来设置source和source handler，除非一个source处理器被找到，否则不应该调用
   * @param {*} source
   */
  _Tech.prototype.setSource = function (source) {
    let sh = _Tech.selectSourceHandler(source, this.options_)

    if (!sh) {
      if (_Tech.nativeSourceHandler) {
        sh = _Tech.nativeSourceHandler
      } else {
        console.log('当前source没有处理函数')
      }
    }

    // 清楚存在的source handler
    this.disposeSourceHandler()
    this.off('dispose', this.disposeSourceHandler)

    if (sh !== _Tech.nativeSourceHandler) {
      this.currentSource_ = source
    }

    this.sourceHandler_ = sh.handleSource(source, this, this.options_)
    this.on('dispose', this.disposeSourceHandler)
  }

  /**
   * tech被清除时清除已存的sourcehandlers和listeners
   */
  _Tech.prototype.disposeSourceHandler = function () {

    // 清除所有的tracks
    if (this.currentSource_) {
      this.clearTracks(['audio', 'video'])
      this.currentSource_ = null
    }

    // 清除 auto-text tracks
    this.cleanupAutoTextTracks()

    if (this.sourceHandler_) {
      if (this.sourceHandler_.dispose) {
        this.sourceHandler_.dispose()
      }
      this.sourceHandler_ = null
    }
  }
}

Component.registerComponent('Tech', Tech)
Tech.registerTech('Tech', Tech)

Tech.defaultTechOrder_ = []

export default Tech
